/**
 * A String is a length-prefixed and zero-terminated collection of Chars.
 */
abstract String: Slice[Char] {
    public static function fromCString(allocator: Box[Allocator], source: CString): String {
        var length = source.length;
        var data: CString = allocator.alloc(length + 1);
        strcpy(data, source);
        return struct String {
            length,
            data,
        };
    }

    public static function slice(source: CString): String {
        return struct String {
            length: strlen(source),
            data: source
        };
    }

    public function compare(other: Self): Bool {
        if this.length != other.length {
            return false;
        }
        for i in 0 ... this.length {
            if this[i] != other[i] {
                return false;
            }
        }
        return true;
    }

    rules {
        (this as CString) => (this.chars as CString);
        (${s: CString} as String) => String.fromCString($s);
        ($this.chars) => $this.data;
        ($this == $other) => $this.compare($other);
        ($this != $other) => !$this.compare($other);
    }
}

// implement Iterable(Char) for String {
//     public function iterator(): Box[Iterator[Char]] {
//         return this.chars as Box[Iterator[Char]];
//     }
// }

// implement Iterable(Char) for CString {
//     public function iterator(): Box[Iterator[Char]] {
//         return this as Box[Iterator[Char]];
//     }
// }

// implement Iterator[Char] for CString {
//     public function next(): (Box[Iterator[Char]], Option[Char]) {
//         if this[0] == 0 {
//             return (this, None);
//         } else {
//             return (this + 1, Some(this[0]));
//         }
//     }
// }

// /**
//  * Allows coercing values of the implementing type into a string.
//  */
// trait ToString {
//     public function toString(): Ptr[String];

//     rules {
//         ($this as String) => ($this.toString());
//     }
// }

// implement ToString for String {
//     public function toString() {
//         return &this;
//     }
// }

// implement ToString for CString {
//     public function toString() {
//         return String.fromCString(this);
//     }
// }

/**
 * A StringBuffer is a dynamically resizing collection (Vector) of characters.
 */
abstract StringBuffer: Vector[Char] {
    public static function new(allocator: Box[Allocator], capacity: Size): StringBuffer using implicit allocator {
        return Vector[Char].new(capacity) as StringBuffer;
    }

    public function add(s: CString) {
        var l = s.length;
        for i in 0 ... l {
            this.push(s[i]);
        }
    }

    public function toString(): String using implicit this.allocator {
        this.push(0);
        var s = String.fromCString(this.data.data);
        this.pop();
        return s;
    }

    rules {
        (${v: Array[Uint8]} as StringBuffer);
        (this as Array[Uint8]);
        (${v: String} as StringBuffer) => (($v as Vector[Uint8]) as Array[Uint8]) as StringBuffer;
        (this as String) => ((this as Array[Uint8]) as Vector[Uint8]) as String;
    }
}

// implement ToString for StringBuffer {
//     public function toString(): String {
//         return this as String;
//     }
// }
